module ASYNC_FIFO
#(
    parameter FIFO_WIDTH = 16,
    parameter FIFO_DEPTH = 16
)
(
    input                       WCLK,
    input                       RCLK,
    input                       RSTN,

    // 写时钟域
    input   [FIFO_WIDTH-1:0]    WDATA,
    input                       WINC,
    // 读时钟域
    output  [FIFO_WIDTH-1:0]    RDATA,
    input                       RINC,
    
    output                      FULL,
    output                      EMPTY
);

reg [$clog2(FIFO_DEPTH):0] extended_addra;
reg [$clog2(FIFO_DEPTH):0] extended_addrb;
wire [$clog2(FIFO_DEPTH):0] extended_addra_grey = (extended_addra >> 1) ^ extended_addra;
wire [$clog2(FIFO_DEPTH):0] extended_addrb_grey = (extended_addrb >> 1) ^ extended_addrb;

reg [$clog2(FIFO_DEPTH):0] extended_addra_grey_q1;
reg [$clog2(FIFO_DEPTH):0] extended_addrb_grey_q1;
reg [$clog2(FIFO_DEPTH):0] extended_addra_grey_q2;
reg [$clog2(FIFO_DEPTH):0] extended_addrb_grey_q2;

wire full_flag;
wire empty_flag;
assign FULL = full_flag;
assign EMPTY = empty_flag;

// B->A打两拍同步
always @(posedge WCLK) begin
    extended_addrb_grey_q1 <= extended_addrb_grey;
    extended_addrb_grey_q2 <= extended_addrb_grey_q1;
end
// A->B打两拍同步
always @(posedge RCLK) begin
    extended_addra_grey_q1 <= extended_addra_grey;
    extended_addra_grey_q2 <= extended_addra_grey_q1;
end

// 写满信号：不在同一圈，其余位相同，在写时钟域
// 读满信号：在同一圈，所有位相同，在读时钟域
// 这些对格雷码同理，前提是FIFO大小是2的指数
assign full_flag = (extended_addrb_grey_q2[$clog2(FIFO_DEPTH)] ^ extended_addra_grey[$clog2(FIFO_DEPTH)]) && (extended_addrb_grey_q2[$clog2(FIFO_DEPTH)-1:0] == extended_addra_grey[$clog2(FIFO_DEPTH)-1:0]);
assign empty_flag = (extended_addra_grey_q2 == extended_addrb_grey);
assign FULL = full_flag;
assign EMPTY = empty_flag;

// 双口RAM例化
DUAL_PORT_RAM #(
    .ADDR_WIDTH ($clog2(FIFO_DEPTH)),
    .DATA_WIDTH (FIFO_WIDTH),
    .RAM_SIZE   (FIFO_DEPTH)
)DUAL_PORT_RAM_INST
(
    .CLKA       (WCLK),
    .CLKB       (RCLK),
    .ADDRA      (extended_addra[$clog2(FIFO_DEPTH)-1:0]),
    .ADDRB      (extended_addrb[$clog2(FIFO_DEPTH)-1:0]),
    .DINA       (WDATA),
    .DOUTB      (RDATA),
    .ENA        (1'b1),
    .ENB        (1'b1),
    .WEA        (WINC & ~full_flag),
    .WEB        (1'b0)
);

always @(posedge WCLK) begin
    if (~RSTN) begin
        extended_addra <= {$clog2(FIFO_DEPTH){1'b0}};
    end
    else begin
        if(WINC & (~full_flag)) begin
            extended_addra <= extended_addra + 'd1;
        end
    end
end

always @(posedge RCLK) begin
    if (~RSTN) begin
        extended_addrb <= {$clog2(FIFO_DEPTH){1'b0}};
    end
    else begin
        if(RINC & (~empty_flag)) begin
            extended_addrb <= extended_addrb + 'd1;
        end
    end
end

endmodule